﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Core.Components;
using MugenInjection.Exceptions;
using Should;

namespace MugenInjectionTest.Converters
{
    [TestClass]
    public class CollectionConverterTest : ConverterTestBase<CollectionConverter>
    {
        [TestMethod]
        public void ArrayParameterTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                var arrayClass = injector.Get<ArrayClass>();
                arrayClass.Strings.Contains(st1).ShouldBeTrue();
                arrayClass.Strings.Contains(st2).ShouldBeTrue();
                arrayClass.Strings.Contains(st3).ShouldBeTrue();

                var strings = new string[] { st1 };
                injector.Bind<string[]>().ToConstant(strings);
                arrayClass = injector.Get<ArrayClass>();
                arrayClass.Strings.ShouldEqual(strings);
            }
        }

        [TestMethod]
        public void CollectionAsIListParameterTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                var collectionClass = injector.Get<IListCollectionClass>();

                collectionClass.Strings.Contains(st1).ShouldBeTrue();
                collectionClass.Strings.Contains(st2).ShouldBeTrue();
                collectionClass.Strings.Contains(st3).ShouldBeTrue();

                var strings = new List<string>() { st1 };
                injector.Bind<IList<string>>().ToConstant(strings);
                collectionClass = injector.Get<IListCollectionClass>();
                collectionClass.Strings.ShouldEqual(strings);
            }
        }

        [TestMethod]
        public void CollectionAsListParameterTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                var collectionClass = injector.Get<ListCollectionClass>();
                collectionClass.Strings.Contains(st1).ShouldBeTrue();
                collectionClass.Strings.Contains(st2).ShouldBeTrue();
                collectionClass.Strings.Contains(st3).ShouldBeTrue();

                var strings = new List<string>() { st1 };
                injector.Bind<List<string>>().ToConstant(strings);
                collectionClass = injector.Get<ListCollectionClass>();
                collectionClass.Strings.ShouldEqual(strings);
            }
        }

        [TestMethod]
        public void CustomCollectionParameterTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                var collectionClass = injector.Get<CustomCollectionClass>();
                collectionClass.Strings.Contains(st1).ShouldBeTrue();
                collectionClass.Strings.Contains(st2).ShouldBeTrue();
                collectionClass.Strings.Contains(st3).ShouldBeTrue();

                var strings = new CustomCollection<string>();
                injector.Bind<CustomCollection<string>>().ToConstant(strings);
                collectionClass = injector.Get<CustomCollectionClass>();
                collectionClass.Strings.ShouldEqual(strings);
            }
        }

        [TestMethod]
        public void CollectionAsIEnumerableParameterTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                var collectionClass = injector.Get<EnumerableCollectionClass>();
                collectionClass.Strings.Contains(st1).ShouldBeTrue();
                collectionClass.Strings.Contains(st2).ShouldBeTrue();
                collectionClass.Strings.Contains(st3).ShouldBeTrue();

                var strings = new List<string>() { st1 };
                injector.Bind<IEnumerable<string>>().ToConstant(strings);
                collectionClass = injector.Get<EnumerableCollectionClass>();
                collectionClass.Strings.ShouldEqual(strings);
            }
        }

        [TestMethod]
        public void TestErrorCollectionParameter()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            using (var injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(CreateNew());

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);
                TestUtils.Catch<ConflictBindingException>(() => injector.Get<ErrorCollectionClass>());
            }
        }

        [TestMethod]
        public void CollectionParameterInChildInjectorTest()
        {
            const string st1 = "st1";
            const string st2 = "st2";
            const string st3 = "st3";
            const string st4Child = "st4Child";
            using (var injector = GetInjector())
            {
                var collectionConverter = CreateNew();
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                injector.Components.BindingActivatorComponent.AddConverter(collectionConverter);

                injector.Bind<string>().ToConstant(st1);
                injector.Bind<string>().ToConstant(st2);
                injector.Bind<string>().ToConstant(st3);

                using (var child = (MugenInjector)injector.CreateChild())
                {
                    collectionConverter.UseParentToResolveCollection = true;
                    child.Bind<string>().ToConstant(st4Child);
                    var collectionClass = child.Get<EnumerableCollectionClass>();
                    collectionClass.Strings.Contains(st1).ShouldBeTrue();
                    collectionClass.Strings.Contains(st2).ShouldBeTrue();
                    collectionClass.Strings.Contains(st3).ShouldBeTrue();
                    collectionClass.Strings.Contains(st4Child).ShouldBeTrue();

                    collectionConverter.UseParentToResolveCollection = false;
                    collectionClass = child.Get<EnumerableCollectionClass>();
                    collectionClass.Strings.Contains(st1).ShouldBeFalse();
                    collectionClass.Strings.Contains(st2).ShouldBeFalse();
                    collectionClass.Strings.Contains(st3).ShouldBeFalse();
                    collectionClass.Strings.Contains(st4Child).ShouldBeTrue();

                    var strings = new List<string>() { st1 };
                    injector.Bind<IEnumerable<string>>().ToConstant(strings);
                    collectionClass = child.Get<EnumerableCollectionClass>();
                    collectionClass.Strings.ShouldEqual(strings);
                }
            }
        }

        public class ArrayClass
        {
            public string[] Strings { get; set; }

            public ArrayClass(string[] strings)
            {
                Strings = strings;
            }
        }

        public class IListCollectionClass
        {
            public IList<string> Strings { get; set; }

            public IListCollectionClass(IList<string> strings)
            {
                Strings = strings;
            }
        }

        public class EnumerableCollectionClass
        {
            public IEnumerable<string> Strings { get; set; }

            public EnumerableCollectionClass(IEnumerable<string> strings)
            {
                Strings = strings;
            }
        }

        public class ListCollectionClass
        {
            public List<string> Strings { get; set; }

            public ListCollectionClass(List<string> strings)
            {
                Strings = strings;
            }
        }

        public class CustomCollectionClass
        {
            public CustomCollection<string> Strings { get; set; }

            public CustomCollectionClass(CustomCollection<string> strings)
            {
                Strings = strings;
            }
        }

        public class ErrorCollectionClass
        {
            public ErrorCollection<string> Strings { get; set; }

            public ErrorCollectionClass(ErrorCollection<string> strings)
            {
                Strings = strings;
            }
        }

        public class ErrorCollection<T> : ICollection<T>
        {
            public ErrorCollection(string st)
            {

            }

            #region Implementation of IEnumerable

            /// <summary>
            /// Returns an enumerator that iterates through the collection.
            /// </summary>
            /// <returns>
            /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>1</filterpriority>
            public IEnumerator<T> GetEnumerator()
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns>
            /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion

            #region Implementation of ICollection<T>

            /// <summary>
            /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
            public void Add(T item)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
            public void Clear()
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
            /// </summary>
            /// <returns>
            /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
            /// </returns>
            /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
            public bool Contains(T item)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
            /// </summary>
            /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.</exception>
            public void CopyTo(T[] array, int arrayIndex)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <returns>
            /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </returns>
            /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
            public bool Remove(T item)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <returns>
            /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </returns>
            public int Count { get; private set; }

            /// <summary>
            /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
            /// </summary>
            /// <returns>
            /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
            /// </returns>
            public bool IsReadOnly { get; private set; }

            #endregion
        }

        public class CustomCollection<T> : ICollection<T>
        {
            private readonly List<T> _list = new List<T>();

            #region Implementation of IEnumerable

            /// <summary>
            /// Returns an enumerator that iterates through the collection.
            /// </summary>
            /// <returns>
            /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>1</filterpriority>
            public IEnumerator<T> GetEnumerator()
            {
                return _list.GetEnumerator();
            }

            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns>
            /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion

            #region Implementation of ICollection<T>

            /// <summary>
            /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
            public void Add(T item)
            {
                _list.Add(item);
            }

            /// <summary>
            /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
            public void Clear()
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
            /// </summary>
            /// <returns>
            /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
            /// </returns>
            /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
            public bool Contains(T item)
            {
                return _list.Contains(item);
            }

            /// <summary>
            /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
            /// </summary>
            /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.</exception>
            public void CopyTo(T[] array, int arrayIndex)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <returns>
            /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </returns>
            /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
            public bool Remove(T item)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </summary>
            /// <returns>
            /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
            /// </returns>
            public int Count { get; private set; }

            /// <summary>
            /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
            /// </summary>
            /// <returns>
            /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
            /// </returns>
            public bool IsReadOnly { get; private set; }

            #endregion
        }

        #region Overrides of ConverterTestBase<CollectionConverter>

        protected override CollectionConverter CreateNew()
        {
            return new CollectionConverter();
        }

        #endregion
    }
}
